interface PlotterParams {
    canvas: Taro.CanvasContext,
    width: number,
    height: number,
    percent: number,
    fontSize?: number,
    color?: string,
    fontColor?: string,
    isLandscape: boolean,
    isShowText: boolean
}

interface PlotterPoint {
    x: number,
    y: number
}

export default class Plotter {

    static startAnimationFrame(action: (begin?: number) => boolean): void {
        let handleTimer: number | null = null
        // 显示器 60帧/每秒 计算延迟多少毫秒绘制一帧
        let frameTime = Math.round(1000 / 60)
        let delay = frameTime
        // 记录开始时间
        let begin = 0
        // 记录结束时间
        let end = 0
        // 记录函数执行过程逝去的时间
        let elapsed = 0
        const running = () => {
            if (handleTimer !== null) {
                clearTimeout(handleTimer)
            }
            begin = +new Date()
            let flag = action(begin)
            if (flag) {
                end = +new Date()
                elapsed = end - begin
                // console.log('绘制耗时:', elapsed)
                delay = frameTime - elapsed
                handleTimer = setTimeout(running, delay)
            }
        }
        running()
    }

    static stopAnimationFrame(handle: number | null) {
        if (handle !== null) {
            clearTimeout(handle)
        }
    }

    canvas: Taro.CanvasContext
    width: number
    height: number
    isLandscape: boolean
    opacity: number
    color: string
    isShowText: boolean
    fontColor: string
    fontSize: number

    center: PlotterPoint
    // 单个波浪曲线的尺寸
    curveSize: {
        width: number,
        height: number
    }

    waves: {
        percent: number,
        color: string,
        backColor: string,
        // 横切面上的贝塞尔路径点总数
        pointAmount: number,
        // 贝塞尔曲线的起始点
        start: PlotterPoint,
        // 贝塞尔曲线的偏移点
        offset: PlotterPoint,
        // 水平线，水面
        lineLevel: number,
        // 摇摆幅度
        swayLevel: number
    }

    // 默认进度值
    defaultPercent: number = 5
    defaultInitPercent: number = -5
    defaultSpeed: number = 0.5

    constructor(params: PlotterParams) {
        this.canvas = params.canvas
        this.width = params.width
        this.height = params.height
        this.isLandscape = params.isLandscape || false
        this.opacity = 0
        this.color = params.color || '#3399FF'
        this.isShowText = params.isShowText || true
        this.fontColor = params.fontColor || '#FFFFFF'
        this.fontSize = params.fontSize || 26

        this.center = {
            x: this.width / 2,
            y: this.height / 2
        }
        this.curveSize = {
            width: 60,
            height: 16
        }
        this.waves = {
            percent: params.percent || this.defaultPercent,
            color: this.color,
            backColor: this.lightenDarkenColor(this.color, -30),
            pointAmount: (this.width + 2 * this.curveSize.width) / this.curveSize.width,
            start: {
                x: -2 * this.curveSize.width,
                y: this.height
            },
            offset: {
                x: 2,
                y: 5
            },
            lineLevel: 0,
            swayLevel: 5
        }
    }

    draw = () => {
        this.canvas.save()
        if (this.isLandscape) {
            this.setRotate()
        }
        (this.canvas as any).globalAlpha = this.opacity
        this.drawCircle()
        this.clipCircle()
        // 画背景波浪
        this.drawWave(0, -this.waves.offset.x, 8, this.waves.backColor)
        // 画前景波浪
        this.drawWave(this.waves.start.x, this.waves.offset.x, 0, this.waves.color)
        if (this.isShowText) {
            this.drawText()
        }
        this.canvas.restore()
        this.canvas.draw()
    }

    drawCircle = () => {
        const clone = (this.canvas as any)
        clone.globalAlpha = this.opacity
        this.canvas.beginPath()
        clone.lineWidth = 2
        clone.strokeStyle = this.color
        this.canvas.arc(
            this.center.x,
            this.center.y,
            this.center.x - 2,
            0,
            2 * Math.PI,
            false
        );
        (this.canvas as any).shadowOffsetX = 0;
        (this.canvas as any).shadowOffsetY = 0;
        (this.canvas as any).shadowColor = this.lightenDarkenColor(this.color, 10);
        (this.canvas as any).shadowBlur = 3
        this.canvas.stroke();
        (this.canvas as any).shadowBlur = 0
    }

    clipCircle = () => {
        this.canvas.beginPath();
        (this.canvas as any).fillStyle = 'transparent'
        this.canvas.arc(
            this.center.x,
            this.center.y,
            this.center.x - 3,
            0,
            2 * Math.PI,
            false
        )
        this.canvas.fill()
        this.canvas.clip()
    }

    drawWave = (startX: number, offsetX: number, offsetHeight: number, color: string) => {
        this.canvas.beginPath()
        this.canvas.moveTo(startX - offsetX, this.waves.start.y - offsetHeight)
        for (let i = 0; i < this.waves.pointAmount; i++) {
            // 计算当前波浪的水平面
            this.waves.lineLevel = i % 2 !== 0 ? this.waves.start.y + this.curveSize.height : this.waves.start.y - this.curveSize.height
            this.canvas.quadraticCurveTo(
                startX + this.curveSize.width / 2 + this.curveSize.width * i - offsetX, this.waves.lineLevel - offsetHeight,
                startX + this.curveSize.width + this.curveSize.width * i - offsetX, this.waves.start.y - offsetHeight
            )
        }
        this.canvas.lineTo(this.width, this.height)
        this.canvas.lineTo(0, this.height);
        // this.canvas.setFillStyle(color)
        (this.canvas as any).fillStyle = color;
        (this.canvas as any).shadowOffsetX = 0;
        (this.canvas as any).shadowOffsetY = 0;
        (this.canvas as any).shadowColor = this.lightenDarkenColor(color, 20);
        (this.canvas as any).shadowBlur = 10
        this.canvas.fill();
        (this.canvas as any).shadowBlur = 0
    }

    drawText = () => {
        this.canvas.beginPath();
        (this.canvas as any).fillStyle = this.fontColor;
        (this.canvas as any).font = 'normal normal ' + this.fontSize + 'px sans-serif'
        this.canvas.setTextBaseline('middle')
        this.canvas.setTextAlign('center')
        this.canvas.fillText(~~this.waves.percent + '%', this.center.x, this.center.y, this.width / 2)
    }

    updateWave = (percent: number) => {
        if (-1 * this.waves.offset.x >= 2 * this.curveSize.width) {
            this.waves.offset.x = 0
        }
        this.waves.offset.x -= this.waves.swayLevel
        this.waves.percent = percent
        if (this.defaultInitPercent < this.defaultPercent) {
            this.defaultInitPercent += this.defaultSpeed
        }
        const value = 1 - this.defaultInitPercent / 100
        const nowValue = 1 - this.waves.percent / 100
        this.waves.offset.y = this.height * value * nowValue
        this.waves.start.y = this.waves.offset.y
    }

    setRotate = () => {
        this.canvas.translate(this.width / 2, this.height / 2)
        this.canvas.rotate(90 * Math.PI / 180)
        this.canvas.translate(-this.width / 2, -this.height / 2)
    }

    setLandscape = (landscape: boolean) => {
        this.isLandscape = landscape
    }

    setOpacity = (opacity: number): void => {
        this.opacity = opacity
    }

    lightenDarkenColor = (col: string, amt: number) => {
        let usePound = false

        if (col[0] === '#') {
            col = col.slice(1)
            usePound = true
        }

        let R = parseInt(col.substring(0, 2), 16)
        let G = parseInt(col.substring(2, 4), 16)
        let B = parseInt(col.substring(4, 6), 16)

        // to make the colour less bright than the input
        // change the following three "+" symbols to "-"
        R = R + amt
        G = G + amt
        B = B + amt

        if (R > 255) R = 255
        else if (R < 0) R = 0

        if (G > 255) G = 255
        else if (G < 0) G = 0

        if (B > 255) B = 255
        else if (B < 0) B = 0

        const RR = ((R.toString(16).length === 1) ? '0' + R.toString(16) : R.toString(16))
        const GG = ((G.toString(16).length === 1) ? '0' + G.toString(16) : G.toString(16))
        const BB = ((B.toString(16).length === 1) ? '0' + B.toString(16) : B.toString(16))

        return (usePound ? '#' : '') + RR + GG + BB
    }
}
